package com.huatai.datacenter.utils;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import io.lettuce.core.ClientOptions;
import io.lettuce.core.SocketOptions;
import io.lettuce.core.cluster.ClusterClientOptions;
import io.lettuce.core.cluster.ClusterTopologyRefreshOptions;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;

/**
 * @author Lion
 * @date 2023/7/24  15:09
 */
@EnableCaching
@Configuration
public class RedisConfiguration {

	@Bean
	@Primary
	public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
		//生成一个默认配置，通过config对象即可对缓存进行自定义配置
		RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
			//默认的缓存超期时间:12小时,也是使用Duration设置
			.entryTtl(Duration.ofHours(12))
			//#设置默认的key和value的序列化类
			.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
			.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new JdkSerializationRedisSerializer()))
			//不缓存空值
			.disableCachingNullValues();

		//使用自定义的缓存配置初始化一个cacheManager
		RedisCacheManager redisCacheManager = RedisCacheManager.builder(connectionFactory)
			.cacheDefaults(config)
			.transactionAware()
			.build();
		return redisCacheManager;
	}

	@Bean
	@ConditionalOnMissingBean(StringRedisTemplate.class)
	public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
		StringRedisTemplate template = new StringRedisTemplate();
		template.setConnectionFactory(redisConnectionFactory);
		return template;
	}

	/**
	 * 重新构建redis模板，
	 * 主要是修改key，value 的序列化方式
	 * 其中存储关键字是字符串，值jackson2JsonRedisSerializer是序列化后的值
	 *
	 * @param redisConnectionFactory
	 * @return
	 */
	@Bean
	@Primary
	public RedisTemplate<String, Object> amyRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
		RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
		redisTemplate.setConnectionFactory(redisConnectionFactory);

		//设置key的序列化方法
		StringRedisSerializer keySerializer = new StringRedisSerializer();
		redisTemplate.setKeySerializer(keySerializer);
		redisTemplate.setHashKeySerializer(keySerializer);


		//RedisSerializer  valueSerializer = new JdkSerializationRedisSerializer();
		//RedisSerializer valueSerializer = new GenericFastJson2JsonRedisSerializer(Object.class);
		//RedisSerializer valueSerializer= new GenericJackson2JsonRedisSerializer();

		//Jackson 无法解决无参构造函数问题
		Jackson2JsonRedisSerializer<Object> valueSerializer = new Jackson2JsonRedisSerializer(Object.class);
		ObjectMapper objectMapper = new ObjectMapper();
		//设置可见度
		objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
		//启动默认的类型
		objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
		objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
		objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
		objectMapper.registerModule(new JavaTimeModule());
		objectMapper.registerModule(new SimpleModule());
		// 反序列化时,遇到未知属性(那些没有对应的属性来映射的属性,并且没有任何setter或handler来处理这样的属性)时是否引起结果失败(通过抛JsonMappingException异常)
		objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

		//解决jackson2无法反序列化localdatetime的问题
		objectMapper.disable(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS);
		// 序列化类，对象映射设置
		valueSerializer.setObjectMapper(objectMapper);


		//设置value的序列化方法(替换默认的JDK序列化)
		redisTemplate.setValueSerializer(valueSerializer);
		redisTemplate.setHashValueSerializer(valueSerializer);


		redisTemplate.afterPropertiesSet();
		return redisTemplate;
	}

	/**
	 * 配置Socket选项
	 * keepAlive=true
	 * tcpNoDelay=true
	 * connectionTimeout=5秒
	 * @return
	 */
	@Bean
	SocketOptions socketOptions(){
		return SocketOptions
			.builder()
			.keepAlive(true)
			.tcpNoDelay(true)
			.connectTimeout(Duration.ofSeconds(5))
			.build();
	}

	/**
	 * 配置客户端选项
	 * @return
	 */
	@Bean
	ClientOptions clientOptions(SocketOptions socketOptions) {
		//开启 自适应集群拓扑刷新和周期拓扑刷新
		ClusterTopologyRefreshOptions clusterTopologyRefreshOptions = ClusterTopologyRefreshOptions
			.builder()
			// 开启全部自适应刷新,若不开启,Redis集群变更时将会导致连接异常
			.enableAllAdaptiveRefreshTriggers()
			// 自适应刷新超时时间(默认30秒),默认关闭开启后时间为30秒
			.adaptiveRefreshTriggersTimeout(Duration.ofSeconds(30))
			// 开周期刷新
			// 默认关闭开启后时间为60秒 ClusterTopologyRefreshOptions.DEFAULT_REFRESH_PERIOD 60  .enablePeriodicRefresh(Duration.ofSeconds(2)) = .enablePeriodicRefresh().refreshPeriod(Duration.ofSeconds(2))
			.enablePeriodicRefresh(Duration.ofSeconds(20))
			.build();
//        ClientOptions clientOptions = ClusterClientOptions.builder()
//                .topologyRefreshOptions(clusterTopologyRefreshOptions).build();
//        clientConfigurationBuilder.clientOptions(clientOptions);

		ClientOptions clientOptions= ClusterClientOptions
			.builder()
			.topologyRefreshOptions(clusterTopologyRefreshOptions)
			.autoReconnect(true)
			.pingBeforeActivateConnection(true)
			.socketOptions(socketOptions)
			.build();
		return  clientOptions;
	}
}
